/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/

package com.chasekernan.hxnova.core.dataholders;

import com.chasekernan.hxnova.core.dataholders.Global;
import com.chasekernan.hxnova.core.players.Player;
import com.chasekernan.hxnova.utils.Utils;
import haxe.Resource;
import haxe.Template;

/**
    Handles all messages and can save each individual's messages at the end of
    a turn. All messages are stored as enums so that they are easily 
    translateable (is that a word?) from data stored in xml files (see 
    data/messages-english.xml).
**/
class MessageHandler {
    
    public static var englishHash = new Hash<String>();
    public static var englishMessages = new EnglishList(englishHash.get);
    
    private static var initialized = false;
    
    /**
        Needs to be called at the beginning of a turn.
    **/
    public static function init() {
        if(initialized) return;
        else initialized = true;
        
        var englishXml = Xml.parse(Resource.getString("messages-english"));
        for (m in englishXml.firstElement().elementsNamed("message")) 
            englishHash.set(m.get("id"), m.firstChild().nodeValue);
    }
    
    public var messages : Array < List < Message >> ;
    
    /**
        Creates a new message handler. NOTE: Global.players needs to be 
        initialized first.
    **/
    public function new() {
        if (Global.players == null) 
            throw "Global.players must be initialized before MessageHandler.";
        
        if(!initialized) init();
        
        messages = new Array();
        for (player in Global.players.players) messages.push(new List());
    }
    
    /**
        Sends a message to a player.
        Will throw an error if the message recipient is the host or if a player
        tries to send a method other than text or a message is addressed as 
        coming from [Everyone].
    **/
    public function sendMessage(message : Message) {
        switch(message.from) {
            case Host:
                //ok
            case Everyone:
                throw "A message can't be sent from everyone...";
            case PlayerTarget(p):
                //rather than having to switch on all the message types, this
                //just uses a quick hack to see if its not [PlainText].
                if (Std.string(message.data).substr(0, 9) != "PlainText") 
                    throw "A player cannot send a message that is not plain "
                            + "text."; 
        }
        
        switch(message.to) {
            case Host:
                throw "Cannot send message to host.";
            
            case Everyone:
                for (p in messages) p.add(message);
            
            case PlayerTarget(p):
                messages[p.id].add(message);
        }
    }
    
    /**
        Converts a message to a string, with the langauge defaulting to English.
    **/
    public static function toString(message : MessageData, ?language : Language)
            : String {
        if (language == null) language = English;
        
        var languageList = switch(language) {
            case English:
                englishMessages;
        }
        
        return switch(message) {
            case PlainText(text):
                text;
            
            case Error(d):
                var t = new Template(languageList.error);
                t.execute( { error: Std.string(d) } );
                
            case ProducedFactories(amount, starName):
                var t = new Template(languageList.producedFactories);
                t.execute( { name : starName, amount : Std.string(amount) } );
                
            case ProducedMines(amount, starName):
                var t = new Template(languageList.producedMines);
                t.execute( { name : starName, amount : Std.string(amount) } );
                
            case StartingMessage(homeworldName):
                var t = new Template(languageList.startingMessage);
                t.execute( { homeworld : homeworldName } );
            
            case TechLevelIncrease(field, to):
                var t = new Template(languageList.techLevelIncrease);
                t.execute( { field : field, to : to } );
                
            case FailedLoadFromStar(name):
                var t = new Template(languageList.failedLoadFromStar);
                t.execute( { name : name } );
                
            case UninhabitedPopDrop(fleet, star):
                var t = new Template(languageList.uninhabitedPopDrop);
                t.execute( { fleet : fleet, star : star } );
                
            case ProducedDefenses(amount, star):
                var t = new Template(languageList.producedDefenses);
                t.execute( { defenses : Std.string(amount), name : star } );
                
            case ProducedScanner(star):
                var t = new Template(languageList.producedScanner);
                t.execute( { name : star } );
                
            case CannotInvade(star):
                var t = new Template(languageList.cannotInvade);
                t.execute( { star : star } );
                
            case FailedInvasion(star, numCombatants):
                var t = new Template(languageList.failedInvasion);
                t.execute( { star : star, 
                        numCombatants : Std.string(numCombatants) } );
                        
            case SuccessfulInvasion(star, numCombatants):
                var t = new Template(languageList.successfulInvasion);
                t.execute( { star : star, 
                        numCombatants : Std.string(numCombatants) } );
                        
            case FailedDefense(star, numCombatants):
                var t = new Template(languageList.failedDefense);
                t.execute( { star : star, 
                        numCombatants : Std.string(numCombatants) } );
                        
            case SuccessfulDefense(star, numCombatants):
                var t = new Template(languageList.successfulDefense);
                t.execute( { star : star, 
                        numCombatants : Std.string(numCombatants) } );
                        
            case FailedColonization(fleet, star):
                var t = new Template(languageList.failedColonization);
                t.execute( { fleet : fleet, star : star } );
                
            case FailedColonizationBattle(star):
                var t = new Template(languageList.failedColonizationBattle);
                t.execute( { star : star } );
                
            case SuccessfulColonizationBattle(star):
                var t = new Template(
						languageList.successfulColonizationBattle);
                t.execute( { star : star } );
                
            case SuccessfulColonization(star):
                var t = new Template(languageList.successfulColonization);
                t.execute( { star : star } );
				
			case TiedInvasion(star, numCombatants):
				var t = new Template(languageList.tiedInvasion);
				t.execute( { star : star, numCombatants : numCombatants } );
            
        }
    }
    
    /**
        Writes an individual message to xml.
    **/
    public static function writeMessageToXml(m : Message) : Xml {
        var xml = Xml.parse("<message></message>").firstChild();
        
        xml.set("to", switch(m.to) {
            case PlayerTarget(p):
                Std.string(p.id);
                
            case Everyone:
                "everyone";
                
            case Host:
                //TODO: Allow message to host? Host target really is just for 
                //messages from host.
                "host";
        });
        
        xml.set("from", switch(m.from) {
            case PlayerTarget(p):
                Std.string(p.id);
                
            case Everyone:
                "everyone";
                
            case Host:
                "host";
        });
        
        xml.set("type", switch(m.data) {
            case PlainText(text):
                xml.firstChild().nodeValue = "<![CDATA[" + text + "]]>";
                "plain_text";
                
            case Error(d):
                xml.set("d", Std.string(d));
                "error";
                
            case ProducedFactories(amount, starName):
                xml.set("amount", Std.string(amount));
                xml.set("star", starName);
                "produced_factories";
                
            case ProducedMines(amount, starName):
                xml.set("amount", Std.string(amount));
                xml.set("star", starName);
                "produced_mines";
                
            case StartingMessage(hw):
                xml.set("homeworld", hw);
                "starting_message";
            
            case TechLevelIncrease(field, to):
                xml.set("field", field);
                xml.set("to", Std.string(to));
                "tech_level_increase";
                
            case FailedLoadFromStar(name):
                xml.set("name", name);
                "failed_load_from_star";
                
            case UninhabitedPopDrop(fleet, star):
                xml.set("fleet", fleet);
                xml.set("star", star);
                "uninhabited_pop_drop";
                
            case ProducedDefenses(amount, star):
                xml.set("amount", Std.string(amount));
                xml.set("star", star);
                "produced_defenses";
                
            case ProducedScanner(star):
                xml.set("star", star);
                "produced_scanner";
                
            case CannotInvade(star):
                xml.set("star", star);
                "cannot_invade";
                
            case FailedInvasion(star, numCombatants):
                xml.set("star", star);
                xml.set("num_combatants", Std.string(numCombatants));
                "failed_invasion";
                
            case SuccessfulInvasion(star, numCombatants):
                xml.set("star", star);
                xml.set("num_combatants", Std.string(numCombatants));
                "successful_invasion";
                
            case FailedDefense(star, numCombatants):
                xml.set("star", star);
                xml.set("num_combatants", Std.string(numCombatants));
                "failed_defense";
                
            case SuccessfulDefense(star, numCombatants):
                xml.set("star", star);
                xml.set("num_combatants", Std.string(numCombatants));
                "successful_defense";
                
            case FailedColonization(fleet, star):
                xml.set("fleet", fleet);
                xml.set("star", star);
                "failed_colonization";
                
            case FailedColonizationBattle(star):
                xml.set("star", star);
                "failed_colonization_battle";
                
            case SuccessfulColonizationBattle(star):
                xml.set("star", star);
                "successful_colonization_battle";
                
            case SuccessfulColonization(star):
                xml.set("star", star);
                "successful_colonization";
                
            case TiedInvasion(star, numCombatants):
                xml.set("star", star);
                xml.set("num_combatants", Std.string(numCombatants));
                "num_combatants";
        });
        
        return xml;
    }
    
    /**
        Reads an individual message from xml.
    **/
    public static function readMessageFromXml(xml : Xml) : Message {
        try {
            var to = switch(xml.get("to")) {
                case "everyone":
                    Everyone;
            
                case "host":
                    throw "Cannot send messages to host.";
                
                default:
                    var id = Std.parseInt(xml.get("to"));
                    PlayerTarget(Global.players.getPlayerById(id));
            }
        
            var from = switch(xml.get("from")) {
                case "everyone":
                    Everyone;
            
                case "host":
                    Host;
                
                default:
                    var id = Std.parseInt(xml.get("from"));
                    PlayerTarget(Global.players.getPlayerById(id));
            }
        
            var data = switch(xml.get("type")) {
                case "plain_text":
                    PlainText(xml.firstChild().nodeValue);
            
                case "error":
                    Error(xml.get("d"));
                
                case "produced_factories":
                    ProducedFactories(Std.parseInt(xml.get("amount")), 
                            xml.get("star"));
                
                case "produced_mines":
                    ProducedMines(Std.parseInt(xml.get("amount")), 
                            xml.get("star"));
                
                case "starting_message":
                    StartingMessage(xml.get("homeworld"));
                    
                case "tech_level_increase":
                    TechLevelIncrease(xml.get("field"), 
                            Std.parseInt(xml.get("to")));
                            
                case "failed_load_from_star":
                    FailedLoadFromStar(xml.get("name"));
                    
                case "uninhabited_pop_drop":
                    UninhabitedPopDrop(xml.get("fleet"), xml.get("star"));
                    
                case "produced_defenses":
                    ProducedDefenses(Std.parseInt(xml.get("amount")), 
                            xml.get("star"));
                            
                case "produced_scanner":
                    ProducedScanner(xml.get("star"));
                    
                case "cannot_invade":
                    CannotInvade(xml.get("star"));
                    
                case "successful_invasion":
                    SuccessfulInvasion(xml.get("star"), Std.parseInt(
                            xml.get("num_combatants")));
                            
                case "failed_invasion":
                    FailedInvasion(xml.get("star"), Std.parseInt(
                            xml.get("num_combatants")));
                            
                case "successful_defense":
                    SuccessfulDefense(xml.get("star"), Std.parseInt(
                            xml.get("num_combatants")));
                            
                case "failed_defense":
                    FailedDefense(xml.get("star"), Std.parseInt(
                            xml.get("num_combatants")));
                            
                case "failed_colonization":
                    FailedColonization(xml.get("fleet"), xml.get("star"));
                    
                case "failed_colonization_battle":
                    FailedColonizationBattle(xml.get("star"));
                    
                case "successful_colonization_battle":
                    SuccessfulColonizationBattle(xml.get("star"));
                    
                case "successful_colonization":
                    SuccessfulColonization(xml.get("star"));
                    
                case "tied_invasion":
                    TiedInvasion(xml.get("star"),
                                 Std.parseInt(xml.get("num_combatants")));
                
                default:
                    throw "Incorrect message data: " + xml.get("type");
            }
        
            return { to: to, from : from, data : data };
        } catch (e : Dynamic) {
            throw "Error while reading message from xml: " + e;
        }
    }
    
    public static var XML_TITLE = "messages";
    
    /**
        Writes all the messages to a player to xml.
        Sample xml:
            TODO: run [writePlayerMessagesToXml]
    **/
    public static function writePlayerMessagesToXml(player : Player) : Xml {
        var m = Global.messages.messages[player.id];
        var xml = Xml.parse('<' + XML_TITLE + '></' + XML_TITLE + '>')
                .firstChild();
        
        for (message in m) {
            //TODO: For an unknown reason, you need to put a spacer in between 
            //xml elemnts otherwise some are skipped...
            xml.addChild(writeMessageToXml(message));
            xml.addChild(Xml.createElement("space"));
        }
        
        return xml;
    }
    
    public static function readPlayerMessagesFromXml(xml : Xml) {
        try {
            var ms = new List<Message>();
        
            for (e in xml.elementsNamed("message")) 
                Global.messages.sendMessage(readMessageFromXml(e));
            
        } catch (e : Dynamic) {
            throw "Error while reading player's messages: " + e;
        }
    }
    
    public static function readPlayerMessagesFromXmlToList(xml : Xml, 
            messages : MessageHandler) {
        try {
            var ms = new List<Message>();
        
            for (e in xml.elementsNamed("message")) 
                messages.sendMessage(readMessageFromXml(e));
            
        } catch (e : Dynamic) {
            throw "Error while reading player's messages: " + e;
        }
    }
    
}

//this is just to make sure that no messages are missing
class EnglishList extends haxe.xml.Proxy<
        "com/chasekernan/hxnova/data/messages-english.xml", String> {
}

/**
    Contains all of the languages supported.
**/
enum Language {
    English;
}

/**
    Who the message is to or from (host is not supported to be the recipient of
    a message though).
**/
enum Target {
    PlayerTarget(p : Player);
    Everyone;
    Host;
}

typedef Message = {
    var from : Target;
    var to : Target;
    var data : MessageData;
}

/**
    Contains all of the possible messages to be sent.
**/
enum MessageData {
    PlainText(text : String);
    Error(d : Dynamic);
    ProducedFactories(amount : Int, starName : String);
    ProducedMines(amount : Int, starName : String);
    ProducedDefenses(amount : Int, starName : String);
    ProducedScanner(starName : String);
    StartingMessage(homeworldName : String);
    TechLevelIncrease(field : String, to : Int);
    FailedLoadFromStar(name : String);
    UninhabitedPopDrop(fleet : String, star : String);
    CannotInvade(star : String);
    FailedInvasion(star : String, numCombatants : Int);
    SuccessfulInvasion(star : String, numCombatants : Int);
    FailedDefense(star : String, numCombatants : Int);
    SuccessfulDefense(star : String, numCombatants : Int);
    FailedColonization(fleet : String, star : String);
    FailedColonizationBattle(star : String);
    SuccessfulColonizationBattle(star : String);
    SuccessfulColonization(star : String);
    TiedInvasion(star : String, numCombatants : Int);
}